Initial basic static delta code drop
authorColin Walters <walters@verbum.org>
Thu, 15 Aug 2013 13:17:37 +0000 (09:17 -0400)
committerColin Walters <walters@verbum.org>
Tue, 4 Feb 2014 15:31:44 +0000 (10:31 -0500)
This has a very basic level of functionality (deltas can be generated,
and applied offline).  There is only some stubbed out pull code to
fetch them via HTTP.

But, better to commit this now and improve it from a known starting
point, rather than have it languish in a branch.

24 files changed:
Makefile-libostree.am
Makefile-ostree.am
Makefile-tests.am
src/libostree/README-deltas.md [new file with mode: 0644]
src/libostree/ostree-core-private.h
src/libostree/ostree-core.c
src/libostree/ostree-core.h
src/libostree/ostree-repo-private.h
src/libostree/ostree-repo-pull.c
src/libostree/ostree-repo-static-delta-compilation.c [new file with mode: 0644]
src/libostree/ostree-repo-static-delta-core.c [new file with mode: 0644]
src/libostree/ostree-repo-static-delta-private.h [new file with mode: 0644]
src/libostree/ostree-repo-static-delta-processing.c [new file with mode: 0644]
src/libostree/ostree-repo.c
src/libostree/ostree-repo.h
src/libostree/ostree-varint.c
src/libostree/ostree-varint.h
src/libotutil/ot-variant-utils.c
src/libotutil/ot-variant-utils.h
src/ostree/main.c
src/ostree/ot-builtin-static-delta.c [new file with mode: 0644]
src/ostree/ot-builtins.h
tests/test-delta.sh [new file with mode: 0755]
tests/test-varint.c

index 060c058c292f14cbbcc333f8453cd46b18b627d1..8e9aaca75f7907534d53c7c18ec230e7673d0a92 100644 (file)
@@ -70,6 +70,10 @@ libostree_1_la_SOURCES = \
        src/libostree/ostree-bootloader-uboot.c \
        src/libostree/ostree-gpg-verifier.c \
        src/libostree/ostree-gpg-verifier.h \
+       src/libostree/ostree-repo-static-delta-core.c \
+       src/libostree/ostree-repo-static-delta-processing.c \
+       src/libostree/ostree-repo-static-delta-compilation.c \
+       src/libostree/ostree-repo-static-delta-private.h \
        $(NULL)
 if USE_LIBARCHIVE
 libostree_1_la_SOURCES += src/libostree/ostree-libarchive-input-stream.h \
index fa1205567e5d0352816a760bd641829ed78876a1..52f50e23a0e8d60f5052e72eae7517a3acdd92b1 100644 (file)
@@ -41,6 +41,7 @@ ostree_SOURCES = src/ostree/main.c \
        src/ostree/ot-builtin-reset.c \
        src/ostree/ot-builtin-rev-parse.c \
        src/ostree/ot-builtin-show.c \
+       src/ostree/ot-builtin-static-delta.c \
        src/ostree/ot-main.h \
        src/ostree/ot-main.c \
        src/ostree/ot-dump.h \
index e9f45f3a10ff072309fad7f797c5e6c80abd0943..e281883f25abee645413c041d732a12e4508e3f7 100644 (file)
@@ -38,6 +38,7 @@ testfiles = test-basic \
        test-admin-deploy-etcmerge-cornercases \
        test-admin-deploy-uboot \
        test-setuid \
+       test-delta \
        test-xattrs \
        $(NULL)
 insttest_SCRIPTS = $(addprefix tests/,$(testfiles:=.sh))
diff --git a/src/libostree/README-deltas.md b/src/libostree/README-deltas.md
new file mode 100644 (file)
index 0000000..28ebcc1
--- /dev/null
@@ -0,0 +1,159 @@
+OSTree Static Object Deltas
+===========================
+
+Currently, OSTree's "archive-z2" mode stores both metadata and content
+objects as individual files in the filesystem.  Content objects are
+zlib-compressed.
+
+The advantage of this is model are:
+
+0) It's easy to understand and implement
+1) Can be served directly over plain HTTP by a static webserver
+2) Space efficient on the server
+
+However, it can be inefficient both for large updates and small ones:
+
+0) For large tree changes (such as going from -runtime to
+   -devel-debug, or major version upgrades), this can mean thousands
+   and thousands of HTTP requests.  The overhead for that is very
+   large (until SPDY/HTTP2.0), and will be catastrophically bad if the
+   webserver is not configured with KeepAlive.
+1) Small changes (typo in gnome-shell .js file) still require around
+   5 metadata HTTP requests, plus a redownload of the whole file.
+
+Why not smart servers?
+======================
+
+Smart servers (custom daemons, or just CGI scripts) as git has are not
+under consideration for this proposal.  OSTree is designed for the
+same use case as GNU/Linux distribution package systems are, where
+content is served by a network of volunteer mirrors that will
+generally not run custom code.
+
+In particular, Amazon S3 style dumb content servers is a very
+important use case, as is being able to apply updates from static
+media like DVD-ROM.
+
+Finding Static Deltas
+=====================
+
+Since static deltas may not exist, the client first needs to attempt
+to locate one.  Suppose a client wants to retrieve commit ${new} while
+currently running ${current}.  The first thing to fetch is the delta
+metadata, called "meta".  It can be found at
+${repo}/deltas/${current}-${new}/meta.
+
+FIXME: GPG signatures (.metameta?)  Or include commit object in meta?
+But we would then be forced to verify the commit only after processing
+the entirety of the delta, which is dangerous.  I think we need to
+require signing deltas.
+
+Delta Bytecode Format
+=====================
+
+A delta-part has the following form:
+
+byte compression-type (0 = none, 'g' = gzip')
+REPEAT[(varint size, delta-part-content)]
+
+delta-part-content:
+  byte[] payload
+  ARRAY[operation]
+
+The rationale for having delta-part is that it allows easy incremental
+resumption of downloads.  The client can look at the delta descriptor
+and skip downloading delta-parts for which it already has the
+contained objects.  This is better than simply resuming a gigantic
+file because if the client decides to fetch a slightly newer version,
+it's very probable that some of the downloading we've already done is
+still useful.
+
+For the actual delta payload, it comes as a stream of pair of
+(payload, operation) so that it can be processed while being
+decompressed.
+
+Finally, the delta-part-content is effectively a high level bytecode
+for a stack-oriented machine.  It iterates on the array of objects in
+order.  The following operations are available:
+
+FETCH
+  Fall back to fetching the current object individually.  Move
+  to the next object.
+
+WRITE(array[(varint offset, varint length)])
+  Write from current input target (default payload) to output.
+
+GUNZIP(array[(varint offset, varint length)])
+  gunzip from current input target (default payload) to output.
+
+CLOSE
+  Close the current output target, and proceed to the next; if the
+  output object was a temporary, the output resets to the current
+  object.
+
+# Change the input source to an object
+READOBJECT(csum object)
+  Set object as current input target
+
+# Change the input source to payload
+READPAYLOAD
+  Set payload as current input target
+
+Compiling Deltas
+================
+
+After reading the above, you may be wondering how we actually *make*
+these deltas.  I envison a strategy similar to that employed by
+Chromium autoupdate:
+http://www.chromium.org/chromium-os/chromiumos-design-docs/autoupdate-details
+
+Something like this would be a useful initial algorithm:
+1) Compute the set of added objects NEW
+2) For each object in NEW:
+  - Look for a the set of "superficially similar" objects in the
+    previous tree, using heuristics based first on filename (including
+    prefix), then on size.  Call this set CANDIDATES.
+    For each entry in CANDIDATES:
+      - Try doing a bup/librsync style rolling checksum, and compute the
+        list of changed blocks.
+      - Try gzip-compressing it
+3) Choose the lowest cost method for each NEW object, and partition
+   the program for each method into deltapart-sized chunks.
+
+However, there are many other possibilities, that could be used in a
+hybrid mode with the above.  For example, we could try to find similar
+objects, and gzip them together.  This would be a *very* useful
+strategy for things like the 9000 Boost headers which have massive
+amounts of redundant data.
+
+Notice too that the delta format supports falling back to retrieving
+individual objects.  For cases like the initramfs which is compressed
+inside the tree with gzip, we're not going to find an efficient way to
+sync it, so the delta compiler should just fall back to fetching it
+individually.
+
+Which Deltas To Create?
+=======================
+
+Going back to the start, there are two cases to optimize for:
+
+1) Incremental upgrades between builds
+2) Major version upgrades
+
+A command line operation would look something like this:
+
+$ ostree --repo=/path/to/repo gendelta --ref-prefix=gnome-ostree/buildmaster/ --strategy=latest --depth=5
+
+This would tell ostree to generate deltas from each of the last 4
+commits to each ref (e.g. gnome-ostree/buildmaster/x86_64-runtime) to
+the latest commit.  It might also be possible of course to have
+--strategy=incremental where we generate a delta between each commit.
+I suspect that'd be something to do if one has a *lot* of disk space
+to spend, and there's a reason for clients to be fetching individual
+refs.
+
+$ ostree --repo=/path/to/repo gendelta --from=gnome-ostree/3.10/x86_64-runtime --to=gnome-ostree/buildmaster/x86_64-runtime
+
+This is an obvious one - generate a delta from the last stable release
+to the current development head.
+
index 84356e739fec14bdcb71c210351e005818642728..851e74b1d7b4bea3fa7dd2a3407b71e7e69234be 100644 (file)
@@ -91,6 +91,16 @@ _ostree_get_relative_object_path (const char        *checksum,
                                   OstreeObjectType   type,
                                   gboolean           compressed);
 
+
+char *
+_ostree_get_relative_static_delta_path (const char        *from,
+                                        const char        *to);
+
+char *
+_ostree_get_relative_static_delta_part_path (const char        *from,
+                                             const char        *to,
+                                             guint              i);
+
 void
 _ostree_loose_path (char              *buf,
                     const char        *checksum,
index fc3d274103c6d01620c0d1ce36c60d8c0e4df7e1..9b7a2379e4e9ede16c6d98425587bf2d28933baa 100644 (file)
@@ -1208,13 +1208,41 @@ ostree_checksum_from_bytes_v (GVariant *csum_v)
  * ostree_checksum_bytes_peek:
  * @bytes: #GVariant of type ay
  *
- * Returns: (transfer none): Binary checksum data in @bytes; do not free
+ * Returns: (transfer none) (array fixed-size=32) (element-type guint8): Binary checksum data in @bytes; do not free.  If @bytes does not have the correct length, return %NULL.
  */
 const guchar *
 ostree_checksum_bytes_peek (GVariant *bytes)
 {
   gsize n_elts;
-  return g_variant_get_fixed_array (bytes, &n_elts, 1);
+  const guchar *ret;
+  ret = g_variant_get_fixed_array (bytes, &n_elts, 1);
+  if (G_UNLIKELY (n_elts != 32))
+    return NULL;
+  return ret;
+}
+
+/**
+ * ostree_checksum_bytes_peek_validate:
+ * @bytes: #GVariant of type ay
+ * @error: Errror
+ *
+ * Like ostree_checksum_bytes_peek(), but also throws @error.
+ *
+ * Returns: (transfer none) (array fixed-size=32) (element-type guint8): Binary checksum data
+ */
+const guchar *
+ostree_checksum_bytes_peek_validate (GVariant  *bytes,
+                                     GError   **error)
+{
+  const guchar *ret = ostree_checksum_bytes_peek (bytes);
+  if (G_UNLIKELY (!ret))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid checksum of length %" G_GUINT64_FORMAT
+                   " expected 32", (guint64) g_variant_n_children (bytes));
+      return NULL;
+    }
+  return ret;
 }
 
 /*
@@ -1293,8 +1321,23 @@ _ostree_get_relative_object_path (const char         *checksum,
   return g_string_free (path, FALSE);
 }
 
+char *
+_ostree_get_relative_static_delta_path (const char        *from,
+                                        const char        *to)
+{
+  return g_strdup_printf ("deltas/%s-%s/meta", from, to);
+}
+
+char *
+_ostree_get_relative_static_delta_part_path (const char        *from,
+                                             const char        *to,
+                                             guint              i)
+{
+  return g_strdup_printf ("deltas/%s-%s/%u", from, to, i);
+}
+
 /*
- * ostree_file_header_parse:
+ * file_header_parse:
  * @metadata: A metadata variant of type %OSTREE_FILE_HEADER_GVARIANT_FORMAT
  * @out_file_info: (out): Parsed file information
  * @out_xattrs: (out): Parsed extended attribute set
@@ -1449,15 +1492,7 @@ gboolean
 ostree_validate_structureof_csum_v (GVariant  *checksum,
                                     GError   **error)
 {
-  gsize n_children = g_variant_n_children (checksum);
-  if (n_children != 32)
-    {
-      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
-                   "Invalid checksum of length %" G_GUINT64_FORMAT
-                   " expected 32", (guint64) n_children);
-      return FALSE;
-    }
-  return TRUE;
+  return ostree_checksum_bytes_peek_validate (checksum, error) != NULL;
 }
 
 /**
index 5c76a4a653af59a79eb72b624c70c8f3f1df570c..bd8e68fcf4fcc3b162f622c7c3547d6efc805809 100644 (file)
@@ -136,6 +136,8 @@ void ostree_checksum_inplace_to_bytes (const char *checksum,
 
 const guchar *ostree_checksum_bytes_peek (GVariant *bytes);
 
+const guchar *ostree_checksum_bytes_peek_validate (GVariant *bytes, GError **error);
+
 int ostree_cmp_checksum_bytes (const guchar *a, const guchar *b);
 
 gboolean ostree_validate_rev (const char *rev, GError **error);
index 711bfea2fce850cff3c76393d527fb701c1c3fa1..b12e116a756612a8a695a4eccddcf3b60a4712db 100644 (file)
@@ -41,6 +41,7 @@ struct OstreeRepo {
   GFile *remote_heads_dir;
   GFile *objects_dir;
   int objects_dir_fd;
+  GFile *deltas_dir;
   GFile *uncompressed_objects_dir;
   int uncompressed_objects_dir_fd;
   GFile *remote_cache_dir;
index efdf1746a4b8ddb8ae9eb7d154cd6f95e7d9eca9..b98d132eedf748b53d48d5eef5166f688b22e4c3 100644 (file)
@@ -58,6 +58,7 @@
 #include "ostree.h"
 #include "ostree-core-private.h"
 #include "ostree-repo-private.h"
+#include "ostree-repo-static-delta-private.h"
 #include "ostree-fetcher.h"
 #include "otutil.h"
 
@@ -97,6 +98,7 @@ typedef struct {
   GThread          *metadata_thread;
   GMainContext     *metadata_thread_context;
   GMainLoop        *metadata_thread_loop;
+  GPtrArray        *static_delta_metas;
   OtWaitableQueue  *metadata_objects_to_scan;
   OtWaitableQueue  *metadata_objects_to_fetch;
   GHashTable       *scanned_metadata; /* Maps object name to itself */
@@ -348,11 +350,13 @@ fetch_uri_sync_on_complete (GObject        *object,
 }
 
 static gboolean
-fetch_uri_contents_utf8_sync (OtPullData  *pull_data,
-                              SoupURI     *uri,
-                              char       **out_contents,
-                              GCancellable  *cancellable,
-                              GError     **error)
+fetch_uri_contents_membuf_sync (OtPullData    *pull_data,
+                                SoupURI        *uri,
+                                gboolean        add_nul,
+                                gboolean        allow_noent,
+                                GBytes        **out_contents,
+                                GCancellable   *cancellable,
+                                GError        **error)
 {
   gboolean ret = FALSE;
   const guint8 nulchar = 0;
@@ -360,6 +364,8 @@ fetch_uri_contents_utf8_sync (OtPullData  *pull_data,
   gs_unref_object GMemoryOutputStream *buf = NULL;
   OstreeFetchUriSyncData fetch_data = { 0, };
 
+  g_assert (error != NULL);
+
   if (g_cancellable_set_error_if_cancelled (cancellable, error))
     return FALSE;
 
@@ -371,7 +377,15 @@ fetch_uri_contents_utf8_sync (OtPullData  *pull_data,
 
   run_mainloop_monitor_fetcher (pull_data);
   if (!fetch_data.result_stream)
-    goto out;
+    {
+      if (g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
+        {
+          g_clear_error (error);
+          ret = TRUE;
+          *out_contents = NULL;
+        }
+      goto out;
+    }
 
   buf = (GMemoryOutputStream*)g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
   if (g_output_stream_splice ((GOutputStream*)buf, fetch_data.result_stream,
@@ -379,14 +393,40 @@ fetch_uri_contents_utf8_sync (OtPullData  *pull_data,
                               cancellable, error) < 0)
     goto out;
 
-  /* Add trailing NUL */
-  if (!g_output_stream_write ((GOutputStream*)buf, &nulchar, 1, cancellable, error))
-    goto out;
+  if (add_nul)
+    {
+      if (!g_output_stream_write ((GOutputStream*)buf, &nulchar, 1, cancellable, error))
+        goto out;
+    }
 
   if (!g_output_stream_close ((GOutputStream*)buf, cancellable, error))
     goto out;
 
-  ret_contents = g_memory_output_stream_steal_data (buf);
+  ret = TRUE;
+  *out_contents = g_memory_output_stream_steal_as_bytes (buf);
+ out:
+  g_clear_object (&(fetch_data.result_stream));
+  return ret;
+}
+
+static gboolean
+fetch_uri_contents_utf8_sync (OtPullData  *pull_data,
+                              SoupURI     *uri,
+                              char       **out_contents,
+                              GCancellable  *cancellable,
+                              GError     **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_bytes GBytes *bytes = NULL;
+  gs_free char *ret_contents = NULL;
+  gsize len;
+
+  if (!fetch_uri_contents_membuf_sync (pull_data, uri, TRUE, FALSE,
+                                       &bytes, cancellable, error))
+    goto out;
+
+  ret_contents = g_bytes_unref_to_data (bytes, &len);
+  bytes = NULL;
 
   if (!g_utf8_validate (ret_contents, -1, NULL))
     {
@@ -398,7 +438,6 @@ fetch_uri_contents_utf8_sync (OtPullData  *pull_data,
   ret = TRUE;
   ot_transfer_out_value (out_contents, &ret_contents);
  out:
-  g_clear_object (&(fetch_data.result_stream));
   return ret;
 }
 
@@ -1193,6 +1232,70 @@ load_remote_repo_config (OtPullData    *pull_data,
   return ret;
 }
 
+static void
+initiate_commit_scan (OtPullData   *pull_data,
+                      const char   *checksum)
+{
+  ot_waitable_queue_push (pull_data->metadata_objects_to_scan,
+                          pull_worker_message_new (PULL_MSG_SCAN,
+                                                   ostree_object_name_serialize (checksum, OSTREE_OBJECT_TYPE_COMMIT)));
+}
+
+#if 0
+static gboolean
+request_static_delta_meta_sync (OtPullData  *pull_data,
+                                const char  *ref,
+                                const char  *checksum,
+                                GVariant   **out_delta_meta,
+                                GCancellable *cancellable,
+                                GError     **error)
+{
+  gboolean ret = FALSE;
+  gs_free char *from_revision = NULL;
+  SoupURI *target_uri = NULL;
+  gs_unref_variant GVariant *ret_delta_meta = NULL;
+
+  if (!ostree_repo_resolve_rev (pull_data->repo, ref, TRUE, &from_revision, error))
+    goto out;
+
+  if (from_revision == NULL)
+    {
+      initiate_commit_scan (pull_data, checksum);
+    }
+  else
+    {
+      gs_free char *delta_name = _ostree_get_relative_static_delta_path (from_revision, checksum);
+      gs_unref_bytes GBytes *delta_meta_data = NULL;
+      gs_unref_variant GVariant *delta_meta = NULL;
+
+      target_uri = suburi_new (pull_data->base_uri, delta_name, NULL);
+
+      if (!fetch_uri_contents_membuf_sync (pull_data, target_uri, FALSE, TRUE,
+                                           &delta_meta_data,
+                                           pull_data->cancellable, error))
+        goto out;
+
+      if (delta_meta_data)
+        {
+          g_print ("Using static delta\n"); 
+          ret_delta_meta = ot_variant_new_from_bytes ((GVariantType*)OSTREE_STATIC_DELTA_META_FORMAT,
+                                                      delta_meta_data, FALSE);
+        }
+    }
+  
+  ret = TRUE;
+  gs_transfer_out_value (out_delta_meta, &ret_delta_meta);
+ out:
+  return ret;
+}
+#endif
+
+static void
+process_one_static_delta_meta (OtPullData   *pull_data,
+                               GVariant     *delta_meta)
+{
+}
+
 gboolean
 ostree_repo_pull (OstreeRepo               *self,
                   const char               *remote_name,
@@ -1207,6 +1310,7 @@ ostree_repo_pull (OstreeRepo               *self,
   gpointer key, value;
   gboolean tls_permissive = FALSE;
   OstreeFetcherConfigFlags fetcher_flags = 0;
+  guint i;
   gs_free char *remote_key = NULL;
   gs_free char *path = NULL;
   gs_free char *baseurl = NULL;
@@ -1293,6 +1397,8 @@ ostree_repo_pull (OstreeRepo               *self,
       goto out;
     }
 
+  pull_data->static_delta_metas = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
+
   requested_refs_to_fetch = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
   updated_refs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
   commits_to_fetch = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
@@ -1364,12 +1470,29 @@ ostree_repo_pull (OstreeRepo               *self,
             {
               const char *branch = *branches_iter;
               char *contents;
+              GVariant *descriptor_data = NULL;
               
               if (!fetch_ref_contents (pull_data, branch, &contents, cancellable, error))
                 goto out;
-              
-              /* Transfer ownership of contents */
-              g_hash_table_insert (requested_refs_to_fetch, g_strdup (branch), contents);
+
+              initiate_commit_scan (pull_data, contents);
+#if 0
+              if (!request_static_delta_meta_sync (pull_data, branch, contents,
+                                                   &descriptor_data, cancellable, error))
+                goto out;
+#endif
+
+              if (!descriptor_data)
+                {
+                  /* Transfer ownership of contents */
+                  g_hash_table_insert (requested_refs_to_fetch, g_strdup (branch), contents);
+                }
+              else
+                {
+                  /* Transfer ownership of delta descriptor */
+                  g_ptr_array_add (pull_data->static_delta_metas, descriptor_data);
+                  g_free (contents);
+                }
             }
         }
     }
@@ -1388,24 +1511,24 @@ ostree_repo_pull (OstreeRepo               *self,
   while (g_hash_table_iter_next (&hash_iter, &key, &value))
     {
       const char *commit = value;
-
-      ot_waitable_queue_push (pull_data->metadata_objects_to_scan,
-                              pull_worker_message_new (PULL_MSG_SCAN,
-                                                       ostree_object_name_serialize (commit, OSTREE_OBJECT_TYPE_COMMIT)));
+      initiate_commit_scan (pull_data, commit);
     }
 
   g_hash_table_iter_init (&hash_iter, requested_refs_to_fetch);
   while (g_hash_table_iter_next (&hash_iter, &key, &value))
     {
       const char *ref = key;
-      const char *sha256 = value;
+      const char *checksum = value;
 
-      ot_waitable_queue_push (pull_data->metadata_objects_to_scan,
-                              pull_worker_message_new (PULL_MSG_SCAN,
-                                                       ostree_object_name_serialize (sha256, OSTREE_OBJECT_TYPE_COMMIT)));
-      g_hash_table_insert (updated_refs, g_strdup (ref), g_strdup (sha256));
+      initiate_commit_scan (pull_data, checksum);
+      g_hash_table_insert (updated_refs, g_strdup (ref), g_strdup (checksum));
     }
-  
+
+  for (i = 0; i < pull_data->static_delta_metas->len; i++)
+    {
+      process_one_static_delta_meta (pull_data, pull_data->static_delta_metas->pdata[i]);
+    }
+
   {
     queue_src = ot_waitable_queue_create_source (pull_data->metadata_objects_to_fetch);
     g_source_set_callback (queue_src, (GSourceFunc)on_metadata_objects_to_fetch_ready, pull_data, NULL);
@@ -1486,6 +1609,7 @@ ostree_repo_pull (OstreeRepo               *self,
                               pull_worker_message_new (PULL_MSG_QUIT, NULL));
       g_thread_join (pull_data->metadata_thread);
     }
+  g_clear_pointer (&pull_data->static_delta_metas, (GDestroyNotify) g_ptr_array_unref);
   g_clear_pointer (&pull_data->metadata_objects_to_scan, (GDestroyNotify) ot_waitable_queue_unref);
   g_clear_pointer (&pull_data->metadata_objects_to_fetch, (GDestroyNotify) ot_waitable_queue_unref);
   g_clear_pointer (&pull_data->scanned_metadata, (GDestroyNotify) g_hash_table_unref);
diff --git a/src/libostree/ostree-repo-static-delta-compilation.c b/src/libostree/ostree-repo-static-delta-compilation.c
new file mode 100644 (file)
index 0000000..cfcf1f4
--- /dev/null
@@ -0,0 +1,380 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013,2014 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "ostree-core-private.h"
+#include "ostree-repo-private.h"
+#include "ostree-repo-static-delta-private.h"
+#include "ostree-diff.h"
+#include "otutil.h"
+#include "ostree-varint.h"
+
+typedef struct {
+  guint64 uncompressed_size;
+  GPtrArray *objects;
+  GString *payload;
+  GString *operations;
+} OstreeStaticDeltaPartBuilder;
+
+typedef struct {
+  GPtrArray *parts;
+} OstreeStaticDeltaBuilder;
+
+static void
+ostree_static_delta_part_builder_unref (OstreeStaticDeltaPartBuilder *part_builder)
+{
+  if (part_builder->objects)
+    g_ptr_array_unref (part_builder->objects);
+  if (part_builder->payload)
+    g_string_free (part_builder->payload, TRUE);
+  if (part_builder->operations)
+    g_string_free (part_builder->operations, TRUE);
+  g_free (part_builder);
+}
+
+static OstreeStaticDeltaPartBuilder *
+allocate_part (OstreeStaticDeltaBuilder *builder)
+{
+  OstreeStaticDeltaPartBuilder *part = g_new0 (OstreeStaticDeltaPartBuilder, 1);
+  part->objects = g_ptr_array_new_with_free_func ((GDestroyNotify)g_variant_unref);
+  part->payload = g_string_new (NULL);
+  part->operations = g_string_new (NULL);
+  part->uncompressed_size = 0;
+  g_ptr_array_add (builder->parts, part);
+  return part;
+}
+
+static GBytes *
+objtype_checksum_array_new (GPtrArray *objects)
+{
+  guint i;
+  GByteArray *ret = g_byte_array_new ();
+
+  g_assert (objects->len > 0);
+  for (i = 0; i < objects->len; i++)
+    {
+      GVariant *serialized_key = objects->pdata[i];
+      OstreeObjectType objtype;
+      const char *checksum;
+      guint8 csum[32];
+      guint8 objtype_v;
+        
+      ostree_object_name_deserialize (serialized_key, &checksum, &objtype);
+      objtype_v = (guint8) objtype;
+
+      ostree_checksum_inplace_to_bytes (checksum, csum);
+
+      g_byte_array_append (ret, &objtype_v, 1);
+      g_byte_array_append (ret, csum, sizeof (csum));
+    }
+  return g_byte_array_free_to_bytes (ret);
+}
+
+static gboolean 
+generate_delta_lowlatency (OstreeRepo                       *repo,
+                           const char                       *from,
+                           const char                       *to,
+                           OstreeStaticDeltaBuilder         *builder,
+                           GCancellable                     *cancellable,
+                           GError                          **error)
+{
+  gboolean ret = FALSE;
+  GHashTableIter hashiter;
+  gpointer key, value;
+  OstreeStaticDeltaPartBuilder *current_part = NULL;
+  gs_unref_object GFile *root_from = NULL;
+  gs_unref_object GFile *root_to = NULL;
+  gs_unref_ptrarray GPtrArray *modified = NULL;
+  gs_unref_ptrarray GPtrArray *removed = NULL;
+  gs_unref_ptrarray GPtrArray *added = NULL;
+  gs_unref_hashtable GHashTable *to_reachable_objects = NULL;
+  gs_unref_hashtable GHashTable *from_reachable_objects = NULL;
+  gs_unref_hashtable GHashTable *new_reachable_objects = NULL;
+
+  if (!ostree_repo_read_commit (repo, from, &root_from, NULL,
+                                cancellable, error))
+    goto out;
+  if (!ostree_repo_read_commit (repo, to, &root_to, NULL,
+                                cancellable, error))
+    goto out;
+
+  /* Gather a filesystem level diff; when we do heuristics to ship
+   * just parts of changed files, we can make use of this data.
+   */
+  modified = g_ptr_array_new_with_free_func ((GDestroyNotify) ostree_diff_item_unref);
+  removed = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+  added = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+  if (!ostree_diff_dirs (OSTREE_DIFF_FLAGS_NONE, root_from, root_to, modified, removed, added,
+                         cancellable, error))
+    goto out;
+
+  if (!ostree_repo_traverse_commit (repo, from, -1, &from_reachable_objects,
+                                    cancellable, error))
+    goto out;
+
+  if (!ostree_repo_traverse_commit (repo, to, -1, &to_reachable_objects,
+                                    cancellable, error))
+    goto out;
+
+  new_reachable_objects = ostree_repo_traverse_new_reachable ();
+
+  g_hash_table_iter_init (&hashiter, to_reachable_objects);
+  while (g_hash_table_iter_next (&hashiter, &key, &value))
+    {
+      GVariant *serialized_key = key;
+
+      if (g_hash_table_contains (from_reachable_objects, serialized_key))
+        continue;
+
+      g_hash_table_insert (new_reachable_objects, g_variant_ref (serialized_key), serialized_key);
+    }
+
+  current_part = allocate_part (builder);
+
+  g_hash_table_iter_init (&hashiter, new_reachable_objects);
+  while (g_hash_table_iter_next (&hashiter, &key, &value))
+    {
+      GVariant *serialized_key = key;
+      const char *checksum;
+      OstreeObjectType objtype;
+      guint64 content_size;
+      gsize object_payload_start;
+      gs_unref_object GInputStream *content_stream = NULL;
+      gsize bytes_read;
+      const guint readlen = 4096;
+
+      ostree_object_name_deserialize (serialized_key, &checksum, &objtype);
+
+      if (!ostree_repo_load_object_stream (repo, objtype, checksum,
+                                           &content_stream, &content_size,
+                                           cancellable, error))
+        goto out;
+
+      current_part->uncompressed_size += content_size;
+
+      /* Ensure we have at least one object per delta, even if a given
+       * object is larger.
+       */
+      if (current_part->objects->len > 0 &&
+          current_part->payload->len + content_size > OSTREE_STATIC_DELTA_PART_MAX_SIZE_BYTES)
+        {
+          current_part = allocate_part (builder);
+        } 
+
+      g_ptr_array_add (current_part->objects, g_variant_ref (serialized_key));
+
+      object_payload_start = current_part->payload->len;
+
+      while (TRUE)
+        {
+          gsize empty_space;
+
+          empty_space = current_part->payload->allocated_len - current_part->payload->len;
+          if (empty_space < readlen)
+            {
+              gsize origlen;
+              origlen = current_part->payload->len;
+              g_string_set_size (current_part->payload, current_part->payload->allocated_len + (readlen - empty_space));
+              current_part->payload->len = origlen;
+            }
+
+          if (!g_input_stream_read_all (content_stream,
+                                        current_part->payload->str + current_part->payload->len,
+                                        readlen,
+                                        &bytes_read,
+                                        cancellable, error))
+            goto out;
+          if (bytes_read == 0)
+            break;
+          
+          current_part->payload->len += bytes_read;
+        }
+      
+      g_string_append_c (current_part->operations, (gchar)OSTREE_STATIC_DELTA_OP_WRITE);
+      _ostree_write_varuint64 (current_part->operations, object_payload_start);
+      _ostree_write_varuint64 (current_part->operations, content_size);
+      g_printerr ("write %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT "\n", (guint64) object_payload_start, (guint64)(content_size));
+      g_string_append_c (current_part->operations, (gchar)OSTREE_STATIC_DELTA_OP_CLOSE);
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+/**
+ * ostree_repo_static_delta_generate:
+ * @self: Repo
+ * @opt: High level optimization choice
+ * @from: ASCII SHA256 checksum of origin
+ * @to: ASCII SHA256 checksum of target
+ * @metadata: (allow-none): Optional metadata
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Generate a lookaside "static delta" from @from which can generate
+ * the objects in @to.  This delta is an optimization over fetching
+ * individual objects, and can be conveniently stored and applied
+ * offline.
+ */
+gboolean
+ostree_repo_static_delta_generate (OstreeRepo                   *self,
+                                   OstreeStaticDeltaGenerateOpt  opt,
+                                   const char                   *from,
+                                   const char                   *to,
+                                   GVariant                     *metadata,
+                                   GCancellable                 *cancellable,
+                                   GError                      **error)
+{
+  gboolean ret = FALSE;
+  OstreeStaticDeltaBuilder builder = { 0, };
+  guint i;
+  GVariant *metadata_source;
+  gs_unref_variant_builder GVariantBuilder *part_headers = NULL;
+  gs_unref_ptrarray GPtrArray *part_tempfiles = NULL;
+  gs_unref_variant GVariant *delta_descriptor = NULL;
+  gs_free char *descriptor_relpath = NULL;
+  gs_unref_object GFile *descriptor_path = NULL;
+  gs_unref_object GFile *descriptor_dir = NULL;
+  gs_unref_variant GVariant *tmp_metadata = NULL;
+
+  builder.parts = g_ptr_array_new_with_free_func ((GDestroyNotify)ostree_static_delta_part_builder_unref);
+
+  /* Ignore optimization flags */
+  if (!generate_delta_lowlatency (self, from, to, &builder,
+                                  cancellable, error))
+    goto out;
+
+  part_headers = g_variant_builder_new (G_VARIANT_TYPE ("a" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT));
+  part_tempfiles = g_ptr_array_new_with_free_func (g_object_unref);
+  for (i = 0; i < builder.parts->len; i++)
+    {
+      OstreeStaticDeltaPartBuilder *part_builder = builder.parts->pdata[i];
+      GBytes *payload_b;
+      GBytes *operations_b;
+      gs_free guchar *part_checksum = NULL;
+      gs_free_checksum GChecksum *checksum = NULL;
+      gs_unref_bytes GBytes *objtype_checksum_array = NULL;
+      gs_unref_bytes GBytes *checksum_bytes = NULL;
+      gs_unref_object GFile *part_tempfile = NULL;
+      gs_unref_object GOutputStream *part_temp_outstream = NULL;
+      gs_unref_object GInputStream *part_in = NULL;
+      gs_unref_object GInputStream *part_payload_in = NULL;
+      gs_unref_object GMemoryOutputStream *part_payload_out = NULL;
+      gs_unref_object GConverterOutputStream *part_payload_compressor = NULL;
+      gs_unref_object GConverter *zlib_compressor = NULL;
+      gs_unref_variant GVariant *delta_part_content = NULL;
+      gs_unref_variant GVariant *delta_part = NULL;
+      gs_unref_variant GVariant *delta_part_header = NULL;
+
+      payload_b = g_string_free_to_bytes (part_builder->payload);
+      part_builder->payload = NULL;
+      
+      operations_b = g_string_free_to_bytes (part_builder->operations);
+      part_builder->operations = NULL;
+      /* FIXME - avoid duplicating memory here */
+      delta_part_content = g_variant_new ("(@ay@ay)",
+                                          ot_gvariant_new_ay_bytes (payload_b),
+                                          ot_gvariant_new_ay_bytes (operations_b));
+      g_variant_ref_sink (delta_part_content);
+
+      /* Hardcode gzip for now */
+      zlib_compressor = (GConverter*)g_zlib_compressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW, 9);
+      part_payload_in = ot_variant_read (delta_part_content);
+      part_payload_out = (GMemoryOutputStream*)g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+      part_payload_compressor = (GConverterOutputStream*)g_converter_output_stream_new ((GOutputStream*)part_payload_out, zlib_compressor);
+
+      if (0 > g_output_stream_splice ((GOutputStream*)part_payload_compressor, part_payload_in,
+                                      G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET | G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
+                                      cancellable, error))
+        goto out;
+
+      /* FIXME - avoid duplicating memory here */
+      delta_part = g_variant_new ("(y@ay)",
+                                  (guint8)'g',
+                                  ot_gvariant_new_ay_bytes (g_memory_output_stream_steal_as_bytes (part_payload_out)));
+
+      if (!gs_file_open_in_tmpdir (self->tmp_dir, 0644,
+                                   &part_tempfile, &part_temp_outstream,
+                                   cancellable, error))
+        goto out;
+      part_in = ot_variant_read (delta_part);
+      if (!ot_gio_splice_get_checksum (part_temp_outstream, part_in,
+                                       &part_checksum,
+                                       cancellable, error))
+        goto out;
+
+      checksum_bytes = g_bytes_new (part_checksum, 32);
+      objtype_checksum_array = objtype_checksum_array_new (part_builder->objects);
+      delta_part_header = g_variant_new ("(@aytt@ay)",
+                                         ot_gvariant_new_ay_bytes (checksum_bytes),
+                                         g_variant_get_size (delta_part),
+                                         part_builder->uncompressed_size,
+                                         ot_gvariant_new_ay_bytes (objtype_checksum_array));
+      g_variant_builder_add_value (part_headers, g_variant_ref (delta_part_header));
+      g_ptr_array_add (part_tempfiles, g_object_ref (part_tempfile));
+    }
+
+  descriptor_relpath = _ostree_get_relative_static_delta_path (from, to);
+  descriptor_path = g_file_resolve_relative_path (self->repodir, descriptor_relpath);
+  descriptor_dir = g_file_get_parent (descriptor_path);
+
+  if (!gs_file_ensure_directory (descriptor_dir, TRUE, cancellable, error))
+    goto out;
+
+  for (i = 0; i < builder.parts->len; i++)
+    {
+      GFile *tempfile = part_tempfiles->pdata[i];
+      gs_free char *part_relpath = _ostree_get_relative_static_delta_part_path (from, to, i);
+      gs_unref_object GFile *part_path = g_file_resolve_relative_path (self->repodir, part_relpath);
+
+      if (!gs_file_rename (tempfile, part_path, cancellable, error))
+        goto out;
+    }
+
+  if (metadata != NULL)
+    metadata_source = metadata;
+  else
+    {
+      GVariantBuilder tmpbuilder;
+      g_variant_builder_init (&tmpbuilder, G_VARIANT_TYPE ("(a(ss)a(say))"));
+      g_variant_builder_add (&tmpbuilder, "a(ss)", NULL);
+      g_variant_builder_add (&tmpbuilder, "a(say)", NULL);
+      tmp_metadata = g_variant_builder_end (&tmpbuilder);
+      g_variant_ref_sink (tmp_metadata);
+      metadata_source = tmp_metadata;
+    }
+
+  delta_descriptor = g_variant_new ("(@(a(ss)a(say))aya(ayttay))",
+                                    metadata_source,
+                                    g_variant_builder_new (G_VARIANT_TYPE ("ay")),
+                                    part_headers);
+
+  if (!ot_util_variant_save (descriptor_path, delta_descriptor, cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  g_clear_pointer (&builder.parts, g_ptr_array_unref);
+  return ret;
+}
diff --git a/src/libostree/ostree-repo-static-delta-core.c b/src/libostree/ostree-repo-static-delta-core.c
new file mode 100644 (file)
index 0000000..3cd706e
--- /dev/null
@@ -0,0 +1,333 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "ostree-repo-private.h"
+#include "ostree-repo-static-delta-private.h"
+#include "otutil.h"
+
+gboolean
+_ostree_static_delta_parse_checksum_array (GVariant      *array,
+                                           guint8       **out_checksums_array,
+                                           guint         *out_n_checksums,
+                                           GError       **error)
+{
+  gsize n = g_variant_n_children (array);
+  guint n_checksums;
+
+  n_checksums = n / OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN;
+
+  if (G_UNLIKELY(n == 0 ||
+                 n > (G_MAXUINT32/OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN) ||
+                 (n_checksums * OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN) != n))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Invalid checksum array length %" G_GSIZE_FORMAT, n);
+      return FALSE;
+    }
+
+  *out_checksums_array = (gpointer)g_variant_get_data (array);
+  *out_n_checksums = n_checksums;
+
+  return TRUE;
+}
+
+
+/**
+ * ostree_repo_list_static_delta_names:
+ * @self: Repo
+ * @out_deltas: (out) (element-type utf8): String name of deltas (checksum-checksum.delta)
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * This function synchronously enumerates all static deltas in the
+ * repository, returning its result in @out_deltas.
+ */ 
+gboolean
+ostree_repo_list_static_delta_names (OstreeRepo                  *self,
+                                     GPtrArray                  **out_deltas,
+                                     GCancellable                *cancellable,
+                                     GError                     **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_ptrarray GPtrArray *ret_deltas = NULL;
+  gs_unref_object GFileEnumerator *dir_enum = NULL;
+
+  ret_deltas = g_ptr_array_new_with_free_func (g_free);
+
+  if (g_file_query_exists (self->deltas_dir, NULL))
+    {
+      dir_enum = g_file_enumerate_children (self->deltas_dir, OSTREE_GIO_FAST_QUERYINFO,
+                                            G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                                            NULL, error);
+      if (!dir_enum)
+        goto out;
+      
+      while (TRUE)
+        {
+          GFileInfo *file_info;
+          GFile *child;
+          const char *name;
+
+          if (!gs_file_enumerator_iterate (dir_enum, &file_info, &child,
+                                           NULL, error))
+            goto out;
+          if (file_info == NULL)
+            break;
+
+          if (g_file_info_get_file_type (file_info) != G_FILE_TYPE_DIRECTORY)
+            continue;
+
+          name = gs_file_get_basename_cached (child);
+
+          {
+            gs_unref_object GFile *meta_path = g_file_get_child (child, "meta");
+
+            if (g_file_query_exists (meta_path, NULL))
+              {
+                g_ptr_array_add (ret_deltas, g_strdup (name));
+              }
+          }
+        }
+    }
+
+  ret = TRUE;
+  gs_transfer_out_value (out_deltas, &ret_deltas);
+ out:
+  return ret;
+}
+
+static gboolean
+have_all_objects (OstreeRepo             *repo,
+                  GVariant               *checksum_array,
+                  gboolean               *out_have_all,
+                  GCancellable           *cancellable,
+                  GError                **error)
+{
+  gboolean ret = FALSE;
+  guint8 *checksums_data;
+  guint i,n_checksums;
+  gboolean have_object = TRUE;
+
+  if (!_ostree_static_delta_parse_checksum_array (checksum_array,
+                                                  &checksums_data,
+                                                  &n_checksums,
+                                                  error))
+    goto out;
+
+  for (i = 0; i < n_checksums; i++)
+    {
+      guint8 objtype = *checksums_data;
+      const guint8 *csum = checksums_data + 1;
+      char tmp_checksum[65];
+
+      if (G_UNLIKELY(!ostree_validate_structureof_objtype (objtype, error)))
+        goto out;
+
+      ostree_checksum_inplace_from_bytes (csum, tmp_checksum);
+
+      if (!ostree_repo_has_object (repo, (OstreeObjectType) objtype, tmp_checksum,
+                                   &have_object, cancellable, error))
+        goto out;
+
+      if (!have_object)
+        break;
+
+      checksums_data += OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN;
+    }
+
+  ret = TRUE;
+  *out_have_all = have_object;
+ out:
+  return ret;
+}
+
+static gboolean
+zlib_uncompress_data (GBytes       *data,
+                      GBytes      **out_uncompressed,
+                      GCancellable *cancellable,
+                      GError      **error)
+{
+  gboolean ret = FALSE;
+  gs_unref_object GMemoryInputStream *memin = (GMemoryInputStream*)g_memory_input_stream_new_from_bytes (data);
+  gs_unref_object GMemoryOutputStream *memout = (GMemoryOutputStream*)g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
+  gs_unref_object GConverter *zlib_decomp =
+    (GConverter*) g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW);
+  gs_unref_object GInputStream *convin = g_converter_input_stream_new ((GInputStream*)memin, zlib_decomp);
+
+  if (0 > g_output_stream_splice ((GOutputStream*)memout, convin,
+                                  G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+                                  G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+                                  cancellable, error))
+    goto out;
+
+  ret = TRUE;
+  *out_uncompressed = g_memory_output_stream_steal_as_bytes (memout);
+ out:
+  return ret;
+}
+
+/**
+ * ostree_repo_static_delta_execute_offline:
+ * @self: Repo
+ * @dir: Path to a directory containing static delta data
+ * @skip_validation: If %TRUE, assume data integrity
+ * @cancellable: Cancellable
+ * @error: Error
+ *
+ * Given a directory representing an already-downloaded static delta
+ * on disk, apply it, generating a new commit.  The directory must be
+ * named with the form "FROM-TO", where both are checksums, and it
+ * must contain a file named "meta", along with at least one part.
+ */
+gboolean
+ostree_repo_static_delta_execute_offline (OstreeRepo                    *self,
+                                          GFile                         *dir,
+                                          gboolean                       skip_validation,
+                                          GCancellable                  *cancellable,
+                                          GError                      **error)
+{
+  gboolean ret = FALSE;
+  guint i, n;
+  gs_unref_object GFile *meta_file = g_file_get_child (dir, "meta");
+  gs_unref_variant GVariant *meta = NULL;
+  gs_unref_variant GVariant *headers = NULL;
+
+  if (!ot_util_variant_map (meta_file, G_VARIANT_TYPE (OSTREE_STATIC_DELTA_META_FORMAT),
+                            FALSE, &meta, error))
+    goto out;
+
+  headers = g_variant_get_child_value (meta, 2);
+  n = g_variant_n_children (headers);
+  for (i = 0; i < n; i++)
+    {
+      guint64 size;
+      guint64 usize;
+      const guchar *csum;
+      gboolean have_all;
+      gs_unref_variant GVariant *header = NULL;
+      gs_unref_variant GVariant *csum_v = NULL;
+      gs_unref_variant GVariant *objects = NULL;
+      gs_unref_object GFile *part_path = NULL;
+      gs_unref_variant GVariant *part = NULL;
+      gs_unref_object GInputStream *raw_in = NULL;
+      gs_unref_object GInputStream *in = NULL;
+
+      header = g_variant_get_child_value (headers, i);
+      g_variant_get (header, "(@aytt@ay)", &csum_v, &size, &usize, &objects);
+
+      if (!have_all_objects (self, objects, &have_all, cancellable, error))
+        goto out;
+
+      /* If we already have these objects, don't bother executing the
+       * static delta.
+       */
+      if (have_all)
+        continue;
+
+      csum = ostree_checksum_bytes_peek_validate (csum_v, error);
+      if (!csum)
+        goto out;
+
+      part_path = ot_gfile_resolve_path_printf (dir, "%u", i);
+
+      in = (GInputStream*)g_file_read (part_path, cancellable, error);
+      if (!in)
+        goto out;
+
+      if (!skip_validation)
+        {
+          gs_unref_object GInputStream *tmp_in = NULL;
+          gs_free guchar *actual_checksum = NULL;
+
+          tmp_in = (GInputStream*)g_file_read (part_path, cancellable, error);
+          if (!tmp_in)
+            goto out;
+
+          if (!ot_gio_checksum_stream (tmp_in, &actual_checksum,
+                                       cancellable, error))
+            goto out;
+
+          if (ostree_cmp_checksum_bytes (csum, actual_checksum) != 0)
+            {
+              g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Checksum mismatch in static delta %s part %u",
+                           gs_file_get_path_cached (dir), i);
+              goto out;
+            }
+        }
+
+      {
+        GMappedFile *mfile = gs_file_map_noatime (part_path, cancellable, error);
+        gs_unref_bytes GBytes *bytes = NULL;
+        gs_unref_bytes GBytes *payload = NULL;
+        gsize partlen;
+        const guint8*partdata;
+
+        if (!mfile)
+          goto out;
+
+        bytes = g_mapped_file_get_bytes (mfile);
+        g_mapped_file_unref (mfile);
+
+        partdata = g_bytes_get_data (bytes, &partlen);
+
+        if (partlen < 1)
+          {
+            g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                         "Corrupted 0 length byte part %s/%i",
+                         gs_file_get_basename_cached (dir),
+                         i);
+            goto out;
+          }
+        
+        switch (partdata[0])
+          {
+          case 0:
+            payload = g_bytes_new_from_bytes (bytes, 1, partlen - 1);
+            break;
+          case 'g':
+            {
+              gs_unref_bytes GBytes *subbytes = g_bytes_new_from_bytes (bytes, 1, partlen - 1);
+              if (!zlib_uncompress_data (subbytes, &payload,
+                                         cancellable, error))
+                goto out;
+            }
+            break;
+          }
+        
+        part = ot_variant_new_from_bytes (G_VARIANT_TYPE (OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT),
+                                          payload, FALSE);
+        
+        
+        if (!_ostree_static_delta_part_execute (self, objects, part, cancellable, error))
+          {
+            g_prefix_error (error, "executing delta part %i: ", i);
+            goto out;
+          }
+      }
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
diff --git a/src/libostree/ostree-repo-static-delta-private.h b/src/libostree/ostree-repo-static-delta-private.h
new file mode 100644 (file)
index 0000000..772a501
--- /dev/null
@@ -0,0 +1,96 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#pragma once
+
+#include "ostree-core.h"
+
+G_BEGIN_DECLS
+
+/* Arbitrarily chosen */
+#define OSTREE_STATIC_DELTA_PART_MAX_SIZE_BYTES (16*1024*1024)
+/* 1 byte for object type, 32 bytes for checksum */
+#define OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN 33
+
+/**
+ * OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT:
+ *
+ *   ay data source
+ *   ay operations
+ */
+#define OSTREE_STATIC_DELTA_PART_PAYLOAD_FORMAT "(ayay)"
+
+/**
+ * OSTREE_STATIC_DELTA_META_ENTRY_FORMAT:
+ *
+ *   ay checksum
+ *   guint64 size:   Total size of delta (sum of parts)
+ *   guint64 usize:   Uncompressed size of resulting objects on disk
+ *   ARRAY[(guint8 objtype, csum object)]
+ *
+ * The checksum is of the delta payload, and each entry in the array
+ * represents an OSTree object which will be created by the deltapart.
+ */
+
+#define OSTREE_STATIC_DELTA_META_ENTRY_FORMAT "(ayttay)"
+
+/**
+ * OSTREE_STATIC_DELTA_META_FORMAT:
+ *
+ * A .delta object is a custom binary format.  It has the following high
+ * level form:
+ *
+ * delta-descriptor:
+ *   metadata: a{sv}
+ *   ARRAY[(csum from, csum to)]: ay
+ *   ARRAY[delta-part-header]
+ *
+ * The metadata would include things like a version number, as well as
+ * extended verification data like a GPG signature.
+ * 
+ * The second array is an array of delta objects that should be
+ * fetched and applied before this one.  This is a fairly generic
+ * recursion mechanism that would potentially allow saving significant
+ * storage space on the server.
+ */ 
+#define OSTREE_STATIC_DELTA_META_FORMAT "(a{sv}aya" OSTREE_STATIC_DELTA_META_ENTRY_FORMAT ")"
+
+gboolean _ostree_static_delta_part_execute (OstreeRepo      *repo,
+                                            GVariant        *header,
+                                            GVariant        *part,
+                                            GCancellable    *cancellable,
+                                            GError         **error);
+
+typedef enum {
+  OSTREE_STATIC_DELTA_OP_FETCH = 1,
+  OSTREE_STATIC_DELTA_OP_WRITE = 2,
+  OSTREE_STATIC_DELTA_OP_GUNZIP = 3,
+  OSTREE_STATIC_DELTA_OP_CLOSE = 4,
+  OSTREE_STATIC_DELTA_OP_READOBJECT = 5,
+  OSTREE_STATIC_DELTA_OP_READPAYLOAD = 6
+} OstreeStaticDeltaOpCode;
+
+gboolean
+_ostree_static_delta_parse_checksum_array (GVariant      *array,
+                                           guint8       **out_checksums_array,
+                                           guint         *out_n_checksums,
+                                           GError       **error);
+G_END_DECLS
+
diff --git a/src/libostree/ostree-repo-static-delta-processing.c b/src/libostree/ostree-repo-static-delta-processing.c
new file mode 100644 (file)
index 0000000..a373f00
--- /dev/null
@@ -0,0 +1,381 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013,2014 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "ostree-repo-private.h"
+#include "ostree-repo-static-delta-private.h"
+#include "otutil.h"
+#include "ostree-varint.h"
+
+/* This should really always be true, but hey, let's just assert it */
+G_STATIC_ASSERT (sizeof (guint) >= sizeof (guint32));
+
+typedef struct {
+  guint           checksum_index;
+  const guint8   *checksums;
+  guint           n_checksums;
+
+  const guint8   *opdata;
+  guint           oplen;
+
+  OstreeObjectType output_objtype;
+  const guint8   *output_target;
+  GFile          *output_tmp_path;
+  GOutputStream  *output_tmp_stream;
+  const guint8   *input_target_csum;
+
+  const guint8   *payload_data;
+  guint64         payload_size; 
+} StaticDeltaExecutionState;
+
+typedef gboolean (*DispatchOpFunc) (OstreeRepo                 *repo,
+                                    StaticDeltaExecutionState  *state,
+                                    GCancellable               *cancellable,
+                                    GError                    **error);
+
+typedef struct  {
+  const char *name;
+  DispatchOpFunc func;
+} OstreeStaticDeltaOperation;
+
+#define OPPROTO(name) \
+  static gboolean dispatch_##name (OstreeRepo                 *repo, \
+                                   StaticDeltaExecutionState  *state, \
+                                   GCancellable               *cancellable, \
+                                   GError                    **error);
+
+OPPROTO(fetch)
+OPPROTO(write)
+OPPROTO(gunzip)
+OPPROTO(close)
+#undef OPPROTO
+
+static OstreeStaticDeltaOperation op_dispatch_table[] = {
+  { "fetch", dispatch_fetch },
+  { "write", dispatch_write },
+  { "gunzip", dispatch_gunzip },
+  { "close", dispatch_close },
+  { NULL }
+};
+
+static gboolean
+open_output_target_csum (OstreeRepo                  *repo,
+                         StaticDeltaExecutionState   *state,
+                         GCancellable                *cancellable,
+                         GError                     **error)
+{
+  gboolean ret = FALSE;
+  guint8 *objcsum;
+
+  g_assert (state->checksums != NULL);
+  g_assert (state->output_target == NULL);
+  g_assert (state->output_tmp_path == NULL);
+  g_assert (state->output_tmp_stream == NULL);
+  g_assert (state->checksum_index < state->n_checksums);
+
+  objcsum = (guint8*)state->checksums + (state->checksum_index * OSTREE_STATIC_DELTA_OBJTYPE_CSUM_LEN);
+
+  if (G_UNLIKELY(!ostree_validate_structureof_objtype (*objcsum, error)))
+    goto out;
+
+  state->output_objtype = (OstreeObjectType) *objcsum;
+  state->output_target = objcsum + 1;
+  if (!gs_file_open_in_tmpdir (repo->tmp_dir, 0644,
+                               &state->output_tmp_path, &state->output_tmp_stream,
+                               cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+
+gboolean
+_ostree_static_delta_part_execute (OstreeRepo      *repo,
+                                   GVariant        *objects,
+                                   GVariant        *part,
+                                   GCancellable    *cancellable,
+                                   GError         **error)
+{
+  gboolean ret = FALSE;
+  guint8 *checksums_data;
+  gs_unref_variant GVariant *checksums = NULL;
+  gs_unref_variant GVariant *payload = NULL;
+  gs_unref_variant GVariant *ops = NULL;
+  StaticDeltaExecutionState statedata = { 0, };
+  StaticDeltaExecutionState *state = &statedata;
+  guint n_executed = 0;
+
+  if (!_ostree_static_delta_parse_checksum_array (objects,
+                                                  &checksums_data,
+                                                  &state->n_checksums,
+                                                  error))
+    goto out;
+
+  state->checksums = checksums_data;
+  g_assert (state->n_checksums > 0);
+  if (!open_output_target_csum (repo, state, cancellable, error))
+    goto out;
+
+  g_variant_get (part, "(@ay@ay)", &payload, &ops);
+
+  state->payload_data = g_variant_get_data (payload);
+  state->payload_size = g_variant_get_size (payload);
+
+  state->oplen = g_variant_n_children (ops);
+  state->opdata = g_variant_get_data (ops);
+  while (state->oplen > 0)
+    {
+      guint8 opcode = state->opdata[0];
+      OstreeStaticDeltaOperation *op;
+
+      if (G_UNLIKELY (opcode == 0 || opcode >= G_N_ELEMENTS (op_dispatch_table)))
+        {
+          g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                       "Out of range opcode %u at offset %u", opcode, n_executed);
+          goto out;
+        }
+      op = &op_dispatch_table[opcode-1];
+      g_printerr ("dispatch %u\n", opcode-1);
+      state->oplen--;
+      state->opdata++;
+      if (!op->func (repo, state, cancellable, error))
+        goto out;
+
+      n_executed++;
+    }
+
+  ret = TRUE;
+ out:
+  return ret;
+}
+
+static gboolean
+dispatch_fetch (OstreeRepo    *repo,   
+                StaticDeltaExecutionState  *state,
+                GCancellable  *cancellable,  
+                GError       **error)
+{
+  g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+               "Static delta fetch opcode is not implemented in this version");
+  return FALSE;
+}
+
+static gboolean
+read_varuint64 (StaticDeltaExecutionState  *state,
+                guint64                    *out_value,
+                GError                    **error)
+{
+  gsize bytes_read;
+  if (!_ostree_read_varuint64 (state->opdata, state->oplen, out_value, &bytes_read))
+    {
+      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                           "Unexpected EOF reading varint");
+      return FALSE;
+    }
+  state->opdata += bytes_read;
+  state->oplen -= bytes_read;
+  return TRUE;
+}
+
+static gboolean
+validate_ofs (StaticDeltaExecutionState  *state,
+              guint64                     offset,
+              guint64                     length,
+              GError                    **error)
+{
+  if (G_UNLIKELY (offset + length < offset ||
+                  offset + length > state->payload_size))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   "Invalid offset/length %" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT,
+                   offset, length);
+      return FALSE;
+    }
+  return TRUE;
+}
+  
+static gboolean
+dispatch_write (OstreeRepo                 *repo,
+                StaticDeltaExecutionState  *state,
+                GCancellable               *cancellable,  
+                GError                    **error)
+{
+  gboolean ret = FALSE;
+  guint64 offset;
+  guint64 length;
+  gsize bytes_written;
+
+  if (G_UNLIKELY(state->oplen < 2))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   "Expected at least 2 bytes for write op");
+      goto out;
+    }
+  if (!read_varuint64 (state, &offset, error))
+    goto out;
+  if (!read_varuint64 (state, &length, error))
+    goto out;
+  
+  if (!validate_ofs (state, offset, length, error))
+    goto out;
+
+  if (!g_output_stream_write_all (state->output_tmp_stream,
+                                  state->payload_data + offset,
+                                  length,
+                                  &bytes_written,
+                                  cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (!ret)
+    g_prefix_error (error, "opcode write: ");
+  return ret;
+}
+
+static gboolean
+dispatch_gunzip (OstreeRepo                 *repo,
+                 StaticDeltaExecutionState  *state,
+                 GCancellable               *cancellable,  
+                 GError                    **error)
+{
+  gboolean ret = FALSE;
+  guint64 offset;
+  guint64 length;
+  gs_unref_object GConverter *zlib_decomp = NULL;
+  gs_unref_object GInputStream *payload_in = NULL;
+  gs_unref_object GInputStream *zlib_in = NULL;
+
+  if (G_UNLIKELY(state->oplen < 2))
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   "Expected at least 2 bytes for gunzip op");
+      goto out;
+    }
+  if (!read_varuint64 (state, &offset, error))
+    goto out;
+  if (!read_varuint64 (state, &length, error))
+    goto out;
+
+  if (!validate_ofs (state, offset, length, error))
+    goto out;
+
+  payload_in = g_memory_input_stream_new_from_data (state->payload_data + offset, length, NULL);
+  zlib_decomp = (GConverter*)g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_RAW);
+  zlib_in = g_converter_input_stream_new (payload_in, zlib_decomp);
+
+  if (0 > g_output_stream_splice (state->output_tmp_stream, zlib_in,
+                                  G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
+                                  cancellable, error))
+    goto out;
+
+  ret = TRUE;
+ out:
+  if (!ret)
+    g_prefix_error (error, "opcode gunzip: ");
+  return ret;
+}
+
+static gboolean
+dispatch_close (OstreeRepo                 *repo,
+                StaticDeltaExecutionState  *state,
+                GCancellable               *cancellable,  
+                GError                    **error)
+{
+  gboolean ret = FALSE;
+  char tmp_checksum[65];
+
+  if (state->checksum_index == state->n_checksums)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+                   "Too many close operations");
+      goto out;
+    }
+
+  g_assert (state->output_tmp_stream);
+  g_assert (state->output_tmp_path);
+
+  if (!g_output_stream_close (state->output_tmp_stream, cancellable, error))
+    goto out;
+
+  g_clear_object (&state->output_tmp_stream);
+
+  ostree_checksum_inplace_from_bytes (state->output_target, tmp_checksum);
+
+  if (OSTREE_OBJECT_TYPE_IS_META (state->output_objtype))
+    {
+      gs_unref_variant GVariant *metadata = NULL;
+
+      if (!ot_util_variant_map (state->output_tmp_path,
+                                ostree_metadata_variant_type (state->output_objtype),
+                                TRUE, &metadata, error))
+        goto out;
+
+      if (!ostree_repo_write_metadata (repo, state->output_objtype, tmp_checksum,
+                                       metadata, NULL, cancellable, error))
+        goto out;
+
+      g_print ("Wrote metadata object '%s'\n",
+               tmp_checksum);
+    }
+  else
+    {
+      gs_unref_object GInputStream *in = NULL;
+      gs_unref_object GFileInfo *info = NULL;
+
+      in = (GInputStream*)g_file_read (state->output_tmp_path, cancellable, error);
+      if (!in)
+        goto out;
+
+      info = g_file_input_stream_query_info ((GFileInputStream*)in, G_FILE_ATTRIBUTE_STANDARD_SIZE,
+                                             cancellable, error);
+      if (!info)
+        goto out;
+      
+      if (!ostree_repo_write_content (repo, tmp_checksum, in,
+                                      g_file_info_get_size (info), NULL,
+                                      cancellable, error))
+        goto out;
+
+      g_print ("Wrote content object '%s'\n",
+               tmp_checksum);
+    }
+
+  state->output_target = NULL;
+  g_clear_object (&state->output_tmp_path);
+
+  state->checksum_index++;
+  if (state->checksum_index < state->n_checksums)
+    {
+      if (!open_output_target_csum (repo, state, cancellable, error))
+        goto out;
+    }
+      
+  ret = TRUE;
+ out:
+  if (!ret)
+    g_prefix_error (error, "opcode close: ");
+  return ret;
+}
index 65c2bdfc8b769c8b8ef049d412cd05ef806feac0..aab625a4cf307de5bd7328eef69409605415c000 100644 (file)
@@ -99,6 +99,7 @@ ostree_repo_finalize (GObject *object)
   g_clear_object (&self->objects_dir);
   if (self->objects_dir_fd != -1)
     (void) close (self->objects_dir_fd);
+  g_clear_object (&self->deltas_dir);
   g_clear_object (&self->uncompressed_objects_dir);
   if (self->uncompressed_objects_dir_fd != -1)
     (void) close (self->uncompressed_objects_dir_fd);
@@ -175,6 +176,8 @@ ostree_repo_constructed (GObject *object)
 
   self->objects_dir = g_file_get_child (self->repodir, "objects");
   self->uncompressed_objects_dir = g_file_resolve_relative_path (self->repodir, "uncompressed-objects-cache/objects");
+  self->deltas_dir = g_file_get_child (self->repodir, "deltas");
+  self->uncompressed_objects_dir = g_file_get_child (self->repodir, "uncompressed-objects-cache");
   self->remote_cache_dir = g_file_get_child (self->repodir, "remote-cache");
   self->config_file = g_file_get_child (self->repodir, "config");
 
index b3f263e8114c7657ff292cfedb3d02394e9190f7..df7f3e0c3971c3b37409bcdadbc7d376d63c4a18 100644 (file)
@@ -426,6 +426,37 @@ gboolean ostree_repo_list_objects (OstreeRepo                  *self,
                                    GCancellable                *cancellable,
                                    GError                     **error);
 
+gboolean ostree_repo_list_static_delta_names (OstreeRepo                  *self,
+                                              GPtrArray                  **out_deltas,
+                                              GCancellable                *cancellable,
+                                              GError                     **error);
+
+/**
+ * OstreeStaticDeltaGenerateOpt:
+ * @OSTREE_STATIC_DELTA_GENERATE_OPT_LOWLATENCY: Optimize for speed of delta creation over space
+ * @OSTREE_STATIC_DELTA_GENERATE_OPT_MAJOR: Optimize for delta size (may be very slow)
+ *
+ * Parameters controlling optimization of static deltas.
+ */
+typedef enum {
+  OSTREE_STATIC_DELTA_GENERATE_OPT_LOWLATENCY,
+  OSTREE_STATIC_DELTA_GENERATE_OPT_MAJOR
+} OstreeStaticDeltaGenerateOpt;
+
+gboolean ostree_repo_static_delta_generate (OstreeRepo                   *self,
+                                            OstreeStaticDeltaGenerateOpt  opt,
+                                            const char                   *from,
+                                            const char                   *to,
+                                            GVariant                     *metadata,
+                                            GCancellable                 *cancellable,
+                                            GError                      **error);
+
+gboolean ostree_repo_static_delta_execute_offline (OstreeRepo                    *self,
+                                                   GFile                         *dir,
+                                                   gboolean                       skip_validation,
+                                                   GCancellable                  *cancellable,
+                                                   GError                      **error);
+
 GHashTable *ostree_repo_traverse_new_reachable (void);
 
 gboolean ostree_repo_traverse_commit (OstreeRepo         *repo,
index 04ef6b846840fe341f759169ee706ef25a9c688b..800aaa6d9dd6d63afb3e70174ac4c2f1106869cd 100644 (file)
@@ -62,13 +62,15 @@ static const int max_varint_bytes = 10;
  * _ostree_read_varuint64:
  * @buf: (array length=buflen): Byte buffer
  * @buflen: Length of bytes in @buf
+ * @out_value: (out): Value
  * @bytes_read: (out): Number of bytes read
  *
- * Returns: An unsigned 64 bit integer value
+ * Returns: %TRUE on success, %FALSE on end of stream
  */
-guint64
+gboolean
 _ostree_read_varuint64 (const guint8   *buf,
                         gsize           buflen,
+                        guint64        *out_value,
                         gsize          *bytes_read)
 {
   guint64 result = 0;
@@ -92,8 +94,9 @@ _ostree_read_varuint64 (const guint8   *buf,
   } while (b & 0x80);
 
   *bytes_read = count;
+  *out_value = result;
 
-  return result;
+  return TRUE;
 }
 
 /**
index 1a47d4d0c6929b5b52f88997b809395c2e10a542..ae1152b88d3cfed973f63390d3283bac1de5e172 100644 (file)
 
 G_BEGIN_DECLS
 
-guint64 _ostree_read_varuint64 (const guint8   *buf,
-                                gsize           buflen,
-                                gsize          *bytes_read);
+gboolean _ostree_read_varuint64 (const guint8   *buf,
+                                 gsize           buflen,
+                                 guint64        *out_value,
+                                 gsize          *bytes_read);
 
 void _ostree_write_varuint64 (GString *buf, guint64 n);
 
index 291f7466482ad4eb3c42ff1647e2df33d207b8fc..0f8e4bee277571b71893d270157fdcd0d13ff3f9 100644 (file)
 #include "config.h"
 
 #include <gio/gio.h>
+#include <gio/gfiledescriptorbased.h>
 
 #include <string.h>
+#include <sys/mman.h>
 
 #include "otutil.h"
 
@@ -149,6 +151,55 @@ ot_util_variant_map (GFile              *src,
   return ret;
 }
 
+typedef struct {
+  gpointer addr;
+  gsize len;
+} VariantMapData;
+
+static void
+variant_map_data_destroy (gpointer data)
+{
+  VariantMapData *mdata = data;
+  (void) munmap (mdata->addr, mdata->len);
+}
+
+gboolean
+ot_util_variant_map_fd (GFileDescriptorBased  *stream,
+                        goffset                start,
+                        const GVariantType    *type,
+                        gboolean               trusted,
+                        GVariant             **out_variant,
+                        GError               **error)
+{
+  gboolean ret = FALSE;
+  gpointer map;
+  struct stat stbuf;
+  VariantMapData *mdata = NULL;
+  gsize len;
+
+  if (!gs_stream_fstat (stream, &stbuf, NULL, error))
+    goto out;
+
+  len = stbuf.st_size - start;
+  map = mmap (NULL, len, PROT_READ, MAP_PRIVATE, 
+              g_file_descriptor_based_get_fd (stream), start);
+  if (!map)
+    {
+      ot_util_set_error_from_errno (error, errno);
+      goto out;
+    }
+
+  mdata = g_new (VariantMapData, 1);
+  mdata->addr = map;
+  mdata->len = len;
+
+  ret = TRUE;
+  *out_variant = g_variant_new_from_data (type, map, len, trusted,
+                                          variant_map_data_destroy, mdata);
+ out:
+  return ret;
+}
+
 /**
  * Read all input from @src, allocating a new #GVariant from it into
  * output variable @out_variant.  @src will be closed as a result.
@@ -219,3 +270,17 @@ ot_util_variant_builder_from_variant (GVariant            *variant,
   return builder;
 }
 
+GVariant *
+ot_variant_new_from_bytes (const GVariantType  *type,
+                           GBytes        *bytes,
+                           gboolean       trusted)
+{
+#if GLIB_VERSION_MIN_REQUIRED >= GLIB_VERSION_2_36
+  return g_variant_new_from_bytes (type, bytes, trusted);
+#else
+  gsize size;
+  gconstpointer data = g_bytes_get_data (bytes, &size);
+  return g_variant_new_from_data (type, data, size, trusted,
+                                  (GDestroyNotify)g_bytes_unref, bytes);
+#endif
+}
index 92746a2d259f3135e7d873e241ce07e945d17bb4..218e5439ff518853e5dea5e978ed4425bfd79c63 100644 (file)
@@ -46,6 +46,13 @@ gboolean ot_util_variant_map (GFile *src,
                               GVariant **out_variant,
                               GError  **error);
 
+gboolean ot_util_variant_map_fd (GFileDescriptorBased *stream,
+                                 goffset              offset,
+                                 const GVariantType  *type,
+                                 gboolean             trusted,
+                                 GVariant           **out_variant,
+                                 GError             **error);
+
 gboolean ot_util_variant_from_stream (GInputStream         *src,
                                       const GVariantType   *type,
                                       gboolean              trusted,
@@ -58,5 +65,10 @@ GInputStream *ot_variant_read (GVariant             *variant);
 GVariantBuilder *ot_util_variant_builder_from_variant (GVariant            *variant,
                                                        const GVariantType  *type);
 
+GVariant *
+ot_variant_new_from_bytes (const GVariantType  *type,
+                           GBytes        *bytes,
+                           gboolean       trusted);
+
 G_END_DECLS
 
index c863252d9610f24e2b143951d80740614017580c..5463ce1b39f982174997e9e95bd8b91b961946cf 100644 (file)
@@ -54,6 +54,7 @@ static OstreeCommand commands[] = {
   { "remote", ostree_builtin_remote, 0 },
   { "rev-parse", ostree_builtin_rev_parse, 0 },
   { "show", ostree_builtin_show, 0 },
+  { "static-delta", ostree_builtin_static_delta, 0 },
 #ifdef HAVE_LIBSOUP 
   { "trivial-httpd", ostree_builtin_trivial_httpd, OSTREE_BUILTIN_FLAG_NO_REPO },
 #endif
diff --git a/src/ostree/ot-builtin-static-delta.c b/src/ostree/ot-builtin-static-delta.c
new file mode 100644 (file)
index 0000000..be535a6
--- /dev/null
@@ -0,0 +1,129 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2013 Colin Walters <walters@verbum.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "ot-builtins.h"
+#include "ostree.h"
+#include "otutil.h"
+
+static char *opt_from_rev;
+static char *opt_to_rev;
+static char *opt_apply;
+
+static GOptionEntry options[] = {
+  { "from", 0, 0, G_OPTION_ARG_STRING, &opt_from_rev, "Create delta from revision REV", "REV" },
+  { "to", 0, 0, G_OPTION_ARG_STRING, &opt_to_rev, "Create delta to revision REV", "REV" },
+  { "apply", 0, 0, G_OPTION_ARG_FILENAME, &opt_apply, "Apply delta from PATH", "PATH" },
+  { NULL }
+};
+
+gboolean
+ostree_builtin_static_delta (int argc, char **argv, OstreeRepo *repo, GCancellable *cancellable, GError **error)
+{
+  gboolean ret = FALSE;
+  GOptionContext *context;
+  gs_unref_ptrarray GPtrArray *delta_names = NULL;
+
+  context = g_option_context_new ("Manage static delta files");
+  g_option_context_add_main_entries (context, options, NULL);
+
+  if (!g_option_context_parse (context, &argc, &argv, error))
+    goto out;
+
+  if (opt_apply)
+    {
+      gs_unref_object GFile *path = g_file_new_for_path (opt_apply);
+
+      if (!ostree_repo_prepare_transaction (repo, NULL, cancellable, error))
+        goto out;
+
+      if (!ostree_repo_static_delta_execute_offline (repo, path, TRUE, cancellable, error))
+        goto out;
+
+      if (!ostree_repo_commit_transaction (repo, NULL, cancellable, error))
+        goto out;
+    }
+  else
+    {
+      if (argc >= 2 && opt_to_rev == NULL)
+        opt_to_rev = argv[1];
+
+      if (argc < 2 && opt_to_rev == NULL)
+        {
+          guint i;
+          if (!ostree_repo_list_static_delta_names (repo, &delta_names, cancellable, error))
+            goto out;
+      
+          if (delta_names->len == 0)
+            {
+              g_print ("(No static deltas)\n");
+            }
+          else
+            {
+              for (i = 0; i < delta_names->len; i++)
+                {
+                  g_print ("%s\n", (char*)delta_names->pdata[i]);
+                }
+            }
+        }
+      else if (opt_to_rev != NULL)
+        {
+          const char *from_source;
+          gs_free char *from_resolved = NULL;
+          gs_free char *to_resolved = NULL;
+          gs_free char *from_parent_str = NULL;
+
+          if (opt_from_rev == NULL)
+            {
+              from_parent_str = g_strconcat (opt_to_rev, "^", NULL);
+              from_source = from_parent_str;
+            }
+          else
+            {
+              from_source = opt_from_rev;
+            }
+
+          if (!ostree_repo_resolve_rev (repo, from_source, FALSE, &from_resolved, error))
+            goto out;
+          if (!ostree_repo_resolve_rev (repo, opt_to_rev, FALSE, &to_resolved, error))
+            goto out;
+
+          g_print ("Generating static delta:\n");
+          g_print ("  From: %s\n", from_resolved);
+          g_print ("  To:   %s\n", to_resolved);
+          if (!ostree_repo_static_delta_generate (repo, OSTREE_STATIC_DELTA_GENERATE_OPT_MAJOR,
+                                                  from_resolved, to_resolved, NULL,
+                                                  cancellable, error))
+            goto out;
+        }
+      else
+        {
+          ot_util_usage_error (context, "--from=REV must be specified", error);
+          goto out;
+        }
+    }
+
+  ret = TRUE;
+ out:
+  if (context)
+    g_option_context_free (context);
+  return ret;
+}
index cbd78471e4ddc2003a9032fbab273cf9d3d3c6c6..fee66f2b4ac8c058289b7e0a738ae84e283580f8 100644 (file)
@@ -45,6 +45,7 @@ BUILTINPROTO(refs);
 BUILTINPROTO(reset);
 BUILTINPROTO(fsck);
 BUILTINPROTO(show);
+BUILTINPROTO(static_delta);
 BUILTINPROTO(rev_parse);
 BUILTINPROTO(remote);
 BUILTINPROTO(write_refs);
diff --git a/tests/test-delta.sh b/tests/test-delta.sh
new file mode 100755 (executable)
index 0000000..c5de5b4
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/bash
+#
+# Copyright (C) 2011,2013 Colin Walters <walters@verbum.org>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+set -e
+
+. $(dirname $0)/libtest.sh
+
+bindatafiles="bash true ostree"
+morebindatafiles="false ls"
+
+echo '1..2'
+
+mkdir repo
+ostree --repo=repo init --mode=archive-z2
+
+mkdir files
+for bin in ${bindatafiles}; do
+    cp $(which ${bin}) files
+done
+
+ostree --repo=repo commit -b test -s test --tree=dir=files
+
+function permuteFile() {
+    permutation=$(($1 % 2))
+    output=$2
+    case $permutation in
+       0) dd if=/dev/zero count=40 bs=1 >> $output;;
+       1) echo aheader | cat - $output >> $output.new && mv $output.new $output;;
+    esac
+}
+
+function permuteDirectory() {
+    permutation=$1
+    dir=$2
+    for x in ${dir}/*; do
+       for z in $(seq ${permutation}); do
+           permuteFile ${z} ${x}
+       done
+    done
+}
+
+permuteDirectory 1 files
+ostree --repo=repo commit -b test -s test --tree=dir=files
+ostree static-delta --repo=repo
+
+origrev=$(ostree --repo=repo rev-parse test^)
+newrev=$(ostree --repo=repo rev-parse test)
+ostree static-delta --repo=repo --from=${origrev} --to=${newrev}
+
+assert_has_dir repo/deltas/${origrev}-${newrev}
+
+mkdir repo2
+ostree --repo=repo2 init --mode=archive-z2
+ostree --repo=repo2 pull-local repo ${origrev}
+
+ostree --repo=repo2 static-delta --apply=repo/deltas/${origrev}-${newrev}
+ostree --repo=repo2 fsck
+ostree --repo=repo2 show ${newrev}
index 9fbc7f38a967481e995507900abbbb00ce787758..1dc6ec1389bf6311376e2b2cf456e8ad4dd18b9b 100644 (file)
@@ -38,7 +38,7 @@ check_one_roundtrip (guint64    val)
       gs_free char *data = g_variant_print (v, FALSE);
       g_test_message ("%" G_GUINT64_FORMAT " -> %s", val, data);
     }
-  newval = _ostree_read_varuint64 ((guint8*)buf->str, buf->len, &bytes_read);
+  g_assert (_ostree_read_varuint64 ((guint8*)buf->str, buf->len, &newval, &bytes_read));
   g_assert_cmpint (bytes_read, <=, 10);
   g_assert_cmpint (val, ==, newval);
 }